#include "memory_manager.h"
#include <algorithm>
#include <ctime>

namespace umtl
{
	//////////////////////////////////////////////////////////////////////////
	//

	class fast_allocator
	{
	public:
		static void * alloc( size_t size );
		static void dealloc( void * p );
	};

	//-----------------------------------------------------------------------
	//

	size_t memory_manager::current_size()
	{
		Locker l(pool_size_mutex);
		return pool_size;
	}

	//-----------------------------------------------------------------------
	//

	size_t memory_manager::alloc_total()
	{
		Locker l(pool_size_mutex);
		return alloc_cumul;
	}

	//-----------------------------------------------------------------------
	//

	size_t memory_manager::dealloc_total()
	{
		Locker l(pool_size_mutex);
		return dealloc_cumul;
	}

	//-----------------------------------------------------------------------
	//

	void * memory_manager::alloc( size_t size )
	{
		void * p = 0;

		if( size > (size_t)_small_size )
		{
			p = big_alloc( size );
		}
		else
		{
			p = small_alloc( size );
		}

		return p;
	}

	//////////////////////////////////////////////////////////////////////////
	//

	void * memory_manager::big_alloc( size_t size )
	{
		Locker l(bic_block_mutex);

		void * p = nullptr;

		auto i = blocks.lower_bound( size );

		if( i == blocks.end() || size <= (i->first / 2) )
		{
			p = new_big_mem( size );
		}
		else
		{
			p = search_big_mem( i, size );

			if( p == nullptr )
			{
				p = new_big_mem( size );
			}
			else
			{
				Locker l(pool_size_mutex);
				pool_size -= size;
			}
		}

		return p;
	}

	//////////////////////////////////////////////////////////////////////////
	//

	void * memory_manager::small_alloc( size_t size )
	{
		size_t * p = (size_t*)fast_allocator::alloc( size + sizeof(size_t) );

		p[0] = size;

		return p+1;
	}

	//////////////////////////////////////////////////////////////////////////
	//

	void memory_manager::free( void * p )
	{
		if(!p) return;

		size_t * rp = (size_t *)(p) - 1;

		size_t size = rp[0];

		if( size > (size_t)_small_size )
		{
			big_free( p );
		}
		else
		{
			small_free( p );
		}
	}

	//////////////////////////////////////////////////////////////////////////
	//

	void memory_manager::big_free( void * p )
	{
		if(!p) return;

		size_t * rp = (size_t *)(p) - 1;

		size_t size = rp[0];

		{				
			Locker l(bic_block_mutex);
			
			blocks[size].push_back( (char*)p );
		}

		{
			Locker l(pool_size_mutex);
			pool_size += size;
		}
	}

	//////////////////////////////////////////////////////////////////////////
	//

	void memory_manager::small_free( void * p )
	{
		if(!p) return;

		fast_allocator::dealloc( ((size_t*)p) - 1 );
	}

	//-----------------------------------------------------------------------
	//

	void * memory_manager::new_big_mem( size_t size )
	{
		size_t need_size = size + sizeof(size_t);

		void* p = new char[ need_size ];

		if(p)
		{
			size_t * s = (size_t*)p;
			s[0] = size;
			p = s + 1;

			{
				Locker l(pool_size_mutex);
				alloc_cumul += size;
			}
		}
		else
		{
			throw std::bad_alloc();
		}

		return p;
	}

	//-----------------------------------------------------------------------
	//

	void * memory_manager::alloc_array( size_t size )
	{
		return alloc(size);
	}

	//-----------------------------------------------------------------------
	//

	void memory_manager::free_array( void * p )
	{
		return free(p);
	}

	//-----------------------------------------------------------------------
	//

	void * memory_manager::search_big_mem( Blocks::iterator blockIter, size_t size )
	{
		void * p = nullptr;

		Chunks & chunks = blockIter->second;

		if ( chunks.empty() == false )
		{
			p = *(chunks.rbegin());

			chunks.pop_back();
		}

		return p;
	}

	//-----------------------------------------------------------------------
	//

	void memory_manager::clear()
	{
		{
			Locker l(bic_block_mutex);

			for( auto i=blocks.begin(); i!=blocks.end(); ++i )
			{
				for( auto j=i->second.begin(); j!=i->second.end(); ++j )
				{
					if( *j )
						delete[] ( (size_t*)(*j) - 1 );
				}
			}

			blocks.clear();
		}
		

		{
			Locker l(pool_size_mutex);
			dealloc_cumul += pool_size;
			pool_size = 0;
		}		
	}

	//-----------------------------------------------------------------------
	//

	memory_manager::~memory_manager()
	{
		clear();
	}

	memory_manager::memory_manager() : pool_size(0), alloc_cumul(0), dealloc_cumul(0)
	{
		std::set_new_handler( outOfMem );
	}

	//-----------------------------------------------------------------------
	//

	memory_manager & memory_manager::get()
	{
		static memory_manager m;
		return m;
	}

	//-----------------------------------------------------------------------
	//

	void * fast_allocator::alloc( size_t size )
	{
		static const int _max_try_count = 10000;

		int try_count = 0;

		void * block = 0;

		for(;;)
		{
			block = malloc( size );

			if( block )
				break;

			if( ++try_count > _max_try_count )
				break;

			// delay
			clock_t startTime = clock();
			static const clock_t max_delay_time = 5;
			for(;;)
			{
				if( clock() - startTime > max_delay_time )
					break;
			}
		}

		return block;
	}

	//-----------------------------------------------------------------------
	//

	void fast_allocator::dealloc( void * p )
	{
		if( p )
			::free(p);
	}

	//-----------------------------------------------------------------------
}